/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.robotplayground.core.program;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.zarubsys.robotplayground.core.Environment;
import net.zarubsys.robotplayground.core.device.IDevice;
import net.zarubsys.robotplayground.core.modules.IModule;
import net.zarubsys.robotplayground.core.modules.IModuleConfiguration;
import net.zarubsys.robotplayground.core.modules.INotificationCause;
import net.zarubsys.robotplayground.core.monitors.IMonitor;

import org.apache.log4j.Logger;

/**
 * API
 * 
 * This class works as the entry point to the module calls in the runtime
 * of the running session. This instance is shared between all objects of the session.
 * 
 * It can be accessed through the environment and vice versa.
 *
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class API {
	
	private static final Logger LOG = Logger.getLogger(API.class);
	
	private final Map<String, IModule<? extends IDevice, ? extends IModuleConfiguration>> availableModulesCache = new HashMap<>();
	private final Map<String, Set<IMonitor>> assignedMonitorsCache = new HashMap<>();

	private Environment env;

	public API(Collection<IModule<? extends IDevice, ? extends IModuleConfiguration>> availableModules, Collection<IMonitor> monitors, Environment env) {
		this.env = env;
		env.setAPI(this);
		for (IModule<? extends IDevice, ? extends IModuleConfiguration> iModule : availableModules) {
			availableModulesCache.put(iModule.getId(), iModule);
			assignedMonitorsCache.put(iModule.getId(), new HashSet<IMonitor>());
		}
		LOG.debug("Registered modules: " + availableModulesCache.keySet());
		
		for (IMonitor monitor : monitors) {
			List<IModule<? extends IDevice, ? extends IModuleConfiguration>> modules = new ArrayList<>();
			for (String requiredModuleID : monitor.getRequiredModules()) {
				IModule<? extends IDevice, ? extends IModuleConfiguration> requiredModule = availableModulesCache.get(requiredModuleID);
				if (requiredModule == null) {
					throw new IllegalArgumentException("Monitor " + monitor + " requires module '" + requiredModuleID + "' which is not available!");
				}
				modules.add(requiredModule);
				assignedMonitorsCache.get(requiredModuleID).add(monitor);
			}
			monitor.init(modules, env);
		}
	}
	
	/**
	 * Call this method if you want to dispatch notification from your module.
	 * For details see documentation of the {@link Environment#dispatchNotification(IModule, INotificationCause)}.
	 */
	public void dispatchNotification(IModule<? extends IDevice, ? extends IModuleConfiguration> module, INotificationCause cause) {
		Set<IMonitor> assignedMonitors = assignedMonitorsCache.get(module.getId());
		for (IMonitor monitor : assignedMonitors) {
			monitor.notify(module, cause);
		}
	}
	
	/**
	 * Call this method if you want to invoke a function in some of the modules.
	 * @param moduleName id of the module which provides the function
	 * @param functionName name of the function to be called
	 * @param values parameters to the function. Must comply with the header of the function.
	 * @return result object of the function or null if the function returns void.
	 */
	public Object callFunction(String moduleName, String functionName, List<Object> values) {
		if (LOG.isDebugEnabled()) {
			LOG.debug("Call to module '" + moduleName + "', function '" + functionName + "'. Values " + values);
		}
		IModule<? extends IDevice, ? extends IModuleConfiguration> iModule = availableModulesCache.get(moduleName);
		if (iModule == null) throw new IllegalArgumentException("Modul for id '" + moduleName + "' was not found! Available modules: " + availableModulesCache.keySet());
		Object result = iModule.callFunction(functionName, values);
		return result;
	}

	/**
	 * THIS METHOD IS NOT MENT TO BE CALLED DIRECTLY
	 * USE {@link Environment#stop(Object, String)} instead.
	 */
	public void stop() {
		for (IModule<? extends IDevice, ? extends IModuleConfiguration> module : availableModulesCache.values()) {
			module.close();
		}
	}
	
	/**
	 * @return module for given id if exists, null otherwise
	 */
	public IModule<? extends IDevice, ? extends IModuleConfiguration> getModule(String id) {
		return availableModulesCache.get(id);
	}
	
	/**
	 * @return assigned environment
	 */
	public Environment getEnvironment() {
		return env;
	}

}

